package com.wutong.erb.service;

import cn.hutool.core.util.RandomUtil;
import com.wutong.erb.dto.UserDTO;
import com.wutong.erb.entity.user.User;
import com.wutong.erb.mapper.UserMapper;
import com.wutong.erb.other.Response;
import com.wutong.erb.util.DeviceUtil;
import com.wutong.erb.util.SecretUtil;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.concurrent.TimeUnit;

import static com.wutong.erb.util.EmailUtil.buildSimpleEmail;

/**
 * 认证服务
 * @author wutong
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class AuthService {

    private final RedisTemplate<String, String> redisTemplate;
    private final JavaMailSender javaMailSender;
    private final UserMapper userMapper;

    @Value("${spring.mail.username}")
    private String from;

    @Value("${account.lock.time}")
    private int lockTime;
    @Value("${account.lock.count}")
    private int lockCount;

    @Async("asyncExecutor")
    public void sendEmailCaptcha(String email) {
        String captcha = RandomUtil.randomString(6);
        log.debug("验证码：{}", captcha);
        redisTemplate.opsForValue().set("EMAIL_CAPTCHA:" + email, captcha, 1, TimeUnit.MINUTES);
        // TODO: 后期需要修改发送邮件的内容样式
        javaMailSender.send(buildSimpleEmail(from, email, "验证码", captcha));
    }


    /**
     * 注册用户 - 逻辑
     * 如果用户类型为教师，则需要指定学校/机构，如果用户类型为普通用户，则不能指定学校/机构，管理员不给予注册权限
     * 注册成功后，需要重新登录来获取JWT Token
     * @param registerDTO 注册信息
     * @return 注册结果
     */
    @Transactional
    public ResponseEntity<?> register(HttpServletRequest request, UserDTO.UserRegisterDTO registerDTO) {
        User user = new User();
        user.setUsername(registerDTO.getUsername());
        user.setPasswordHash(SecretUtil.encrypt(registerDTO.getPassword()));
        // 验证邮箱验证码
        if (redisTemplate.hasKey("EMAIL_CAPTCHA:" + registerDTO.getEmail())) {
            String captcha = redisTemplate.opsForValue().get("EMAIL_CAPTCHA:" + registerDTO.getEmail());
            if (captcha == null) {
                return Response.failure().message("验证码已过期").build().unauthorized();
            }
            if (captcha.equals(registerDTO.getVerificationEmailCode())) {
                return Response.failure().message("验证码错误").build().unauthorized();
            }
        }
        user.setEmail(registerDTO.getEmail());
        String deviceHash = DeviceUtil.generateDeviceHash(request);
        String imageCaptcha = registerDTO.getVerificationCode();
        if (redisTemplate.hasKey("CAPTCHA:" + deviceHash)) {
            String captcha = redisTemplate.opsForValue().get("CAPTCHA:" + deviceHash);
            if (captcha == null) {
                return Response.failure().message("验证码已过期").build().unauthorized();
            }
            // 验证码无视大小写
            if (!captcha.equalsIgnoreCase(imageCaptcha)) {
                return Response.failure().message("验证码错误").build().unauthorized();
            }
        }
        // 不用区分注册类型，统一为普通用户，教师需要单独在注册登录之后申请成为教师
        user.setUserType(User.UserType.NORMAL);
        userMapper.insert(user);
        return Response.success().message("注册成功").build().ok();
    }

    public enum LoginStatus {
        SUCCESS,
        CAPTCHA_REQUIRED,
        CAPTCHA_FAILED,
        LOCKED,
        FAILED
    }

    public LoginStatus login(String deviceHash, UserDTO.UserLoginDTO loginDTO) {
        if (loginDTO.getUsername() == null && loginDTO.getEmail() == null) {
            return LoginStatus.FAILED;
        }
        User user = userMapper.selectByUsername(loginDTO.getUsername());
        if (user == null) {
            user = userMapper.selectByEmail(loginDTO.getUsername());
        }
        if (user == null) {
            return LoginStatus.FAILED;
        }
        if (user.getStatus() == User.UserStatus.DISABLE) {
            return LoginStatus.LOCKED;
        }
        if (isLoginLocked(deviceHash)) {
            return LoginStatus.LOCKED;
        }
        if (shouldNeedCaptcha(deviceHash) && !redisTemplate.hasKey("CAPTCHA-LOGIN:" + deviceHash)) {
            return LoginStatus.CAPTCHA_REQUIRED;
        }
        if (shouldNeedCaptcha(deviceHash)) {
            String captcha = redisTemplate.opsForValue().get("CAPTCHA-LOGIN:" + deviceHash);
            if (captcha == null) {
                return LoginStatus.CAPTCHA_REQUIRED;
            }
            if (!captcha.equals(loginDTO.getVerificationCode())) {
                increaseLoginFailedCount(deviceHash);
                return LoginStatus.CAPTCHA_FAILED;
            }
        }
        if (SecretUtil.check(loginDTO.getPassword(), user.getPasswordHash())) {
            resetLoginFailedCount(deviceHash);
            return LoginStatus.SUCCESS;
        } else {
            increaseLoginFailedCount(deviceHash);
            return LoginStatus.FAILED;
        }
    }

    public boolean shouldNeedCaptcha(String deviceHash) {
        return redisTemplate.hasKey("CAPTCHA-LOGIN:" + deviceHash);
    }

    public int failedLoginCount(String deviceHash) {
        String countStr = redisTemplate.opsForValue().get("LOGIN_FAILED_COUNT:" + deviceHash);
        if (countStr == null) {
            return 0;
        }
        return Integer.parseInt(countStr);
    }

    public void resetLoginFailedCount(String deviceHash) {
        redisTemplate.delete("LOGIN_FAILED_COUNT:" + deviceHash);
    }

    public void increaseLoginFailedCount(String deviceHash) {
        String countStr = redisTemplate.opsForValue().get("LOGIN_FAILED_COUNT:" + deviceHash);
        int count = 1;
        if (countStr != null) {
            count = Integer.parseInt(countStr);
        }
        if (count >= lockCount) {
            redisTemplate.opsForValue().set("LOGIN_LOCK:" + deviceHash, "true", lockTime, TimeUnit.MINUTES);
        } else {
            redisTemplate.opsForValue().set("LOGIN_FAILED_COUNT:" + deviceHash, String.valueOf(count + 1), 1, TimeUnit.MINUTES);
        }
    }

    public boolean isLoginLocked(String deviceHash) {
        return redisTemplate.hasKey("LOGIN_LOCK:" + deviceHash);
    }
}
